package com.har01d.lang.compiler.visitor;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import org.objectweb.asm.Opcodes;

import com.har01d.lang.antlr.Har01dBaseVisitor;
import com.har01d.lang.antlr.Har01dParser;
import com.har01d.lang.compiler.domain.ClassDeclaration;
import com.har01d.lang.compiler.domain.MetaData;
import com.har01d.lang.compiler.domain.Scope;
import com.har01d.lang.compiler.domain.ScopeType;
import com.har01d.lang.compiler.domain.function.Constructor;
import com.har01d.lang.compiler.domain.function.Function;
import com.har01d.lang.compiler.domain.function.FunctionSignature;
import com.har01d.lang.compiler.domain.statement.Block;
import com.har01d.lang.compiler.domain.statement.FieldDeclaration;
import com.har01d.lang.compiler.domain.type.BuiltInType;
import com.har01d.lang.compiler.domain.type.Type;
import com.har01d.lang.compiler.util.FunctionUtil;
import com.har01d.lang.compiler.visitor.function.FunctionVisitor;
import com.har01d.lang.compiler.visitor.statement.expression.ExpressionVisitor;

public class ClassVisitor extends Har01dBaseVisitor<ClassDeclaration> {

    private final Scope scope;

    public ClassVisitor(Scope scope) {
        this.scope = scope;
    }

    @Override
    public ClassDeclaration visitClassDeclaration(Har01dParser.ClassDeclarationContext ctx) {
        String className = ctx.className().getText();
        Scope scope = new Scope(new MetaData(className, true), ScopeType.CLASS, this.scope);

        ExpressionVisitor expressionVisitor = new ExpressionVisitor(scope);
        FieldVisitor fieldVisitor = new FieldVisitor(expressionVisitor, scope);

        // must before constructors
        List<FieldDeclaration> fields = ctx.classBody().field().stream().map(e -> e.accept(fieldVisitor))
                                        .collect(Collectors.toList());

        // must before constructors, add signatures to scope
        FunctionUtil.getFunctionSignatures(ctx.classBody().function(), scope);

        List<Function> constructors = FunctionUtil.getConstructors(ctx.classBody().constructor(), scope);

        List<Function> functions = new ArrayList<>();
        // must after constructors, so fields are initialized
        for (Har01dParser.FunctionContext functionContext : ctx.classBody().function()) {
            functions.add(functionContext.accept(new FunctionVisitor(scope)));
        }

        // add default constructor if required
        if (constructors.isEmpty()) {
            constructors.add(generateDefaultConstructor(scope));
        }

        // constructors initialize fields
        for (Function function : constructors) {
            Constructor constructor = (Constructor) function;
            constructor.setFields(fields);
        }

        return new ClassDeclaration(className, fields, constructors, functions);
    }

    private Constructor generateDefaultConstructor(Scope scope) {
        String name = scope.getClassName();
        Type type = BuiltInType.VOID;
        int flag = Opcodes.ACC_PUBLIC;
        FunctionSignature signature = new FunctionSignature(name, "<init>", new ArrayList<>(), flag, type);
        Block block = Block.empty(scope);
        return new Constructor(signature, block);
    }

}
